/*
	This file is part of Posixcpp library.

	Posixcpp library is free software: you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public License
	as published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	Posixcpp library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU LesserGeneral Public
	License along with Posixcpp library. If not, see
	<http://www.gnu.org/licenses/>.
 */

#include <cstring>
#include <sstream>
#include <iomanip>
#include <bitset>

#include "posixcpp_bytearray.hpp"

#define USER_DATA (this->m_ownsData ? (this->m_data+sizeof(ByteArray*)) : \
									    this->m_data)
#define WRAPPER *reinterpret_cast<posixcpp::ByteArray**>(this->m_data)

using posixcpp::ByteArray;

ByteArray::ByteArray( ) :
m_ownsData( false ),
m_data( NULL ),
m_size( 0 ),
m_capacity( 0 )
{
}

ByteArray::ByteArray( const ByteArray & other )
{
	if (&other == this)
		return;

	this->m_ownsData = false;
	this->m_data = NULL;
	this->m_size = 0UL;
	this->m_capacity = 0UL;

	copyExistingData( (other.m_ownsData ? other.m_data+sizeof(ByteArray*) :
										  other.m_data), other.size() );
}

ByteArray & ByteArray::operator=(const ByteArray & other)
{
	if (&other != this)
		copyExistingData( (other.m_ownsData ? other.m_data+sizeof(ByteArray*) :
										      other.m_data), other.size() );
	return *this;
}

ByteArray::ByteArray( const ByteArray && other )
{
	if (&other == this)
		return;

	this->m_ownsData = other.m_ownsData;
	this->m_data = other.m_data;
	WRAPPER = this;
	this->m_size = other.m_size;
	this->m_capacity = other.m_capacity;
}

ByteArray::ByteArray( size_t size, uint8_t fillChar ) :
m_data(NULL)
{
	resize(size);
	this->fill(fillChar);
}

ByteArray::ByteArray( const uint8_t * data, size_t size ) :
m_ownsData( false ),
m_data( NULL ),
m_size( 0UL ),
m_capacity( 0UL )
{
	copyExistingData(data, size);
}

ByteArray::~ByteArray( )
{
	clear();
}

uint8_t* ByteArray::data( )
{
	PRECONDITION_ASSERT(m_ownsData ? (WRAPPER == this) : true,
					 "Pointer to instance mismatch");
	return USER_DATA;
}

const uint8_t* ByteArray::data( ) const
{
	return this->data();
}

void ByteArray::copyExistingData( const uint8_t * data, size_t size )
{
	PRECONDITION_ASSERT( data != NULL, "Data is null" );
	PRECONDITION_ASSERT( size > 0UL, "Data is empty" );
	
	resize(size);
	
	std::memcpy(USER_DATA, data, size);
}

void ByteArray::clear( )
{
	if (m_ownsData && m_data)
	{
		std::free(m_data);
		m_capacity = 0UL;
	}
	
	m_data = NULL,
	m_size = 0UL;
	m_ownsData = false;
}

void ByteArray::reserve( size_t capacity )
{
	if (m_data == NULL)
	{
		POSIX_CALL_ASSERT_X( (m_data = (uint8_t*)std::malloc(
				capacity+sizeof(ByteArray*) )) != NULL,
						"Unable to reserve data");
		WRAPPER = this;
		m_capacity = capacity;
		m_ownsData = true;
	}
	else if (m_ownsData)
	{
		if (capacity > m_capacity)
		{
			POSIX_CALL_ASSERT_X( (m_data = (uint8_t*)std::realloc(
					m_data, capacity+sizeof(ByteArray*))) != NULL,
							 "Unable to reserve data");
			
			m_capacity = capacity;
			WRAPPER = this;
		}
	}
	else
	{
		if (capacity > m_capacity)
			throw std::runtime_error("cannot extend while not owning data");
	}
}

void ByteArray::resize( size_t size )
{
	reserve(size);
	m_size = size;
}

void ByteArray::compact( )
{
	if (m_data && m_ownsData)
		if (m_size < m_capacity)
		{
			POSIX_CALL_ASSERT_X( (m_data = (uint8_t*)std::realloc(
					m_data, m_size+sizeof(ByteArray*))) != NULL,
							 "Unable to reserve data");
			m_capacity = m_size;
			WRAPPER = this;
		}
}

void ByteArray::fill( uint8_t byte, size_t start, size_t count )
{
	PRECONDITION_ASSERT( m_size>0, "Empty array" );
	PRECONDITION_ASSERT( start<m_size, "Start out of range" );
	PRECONDITION_ASSERT( (start+count)<=m_size, "End out of range" );
	
	if ( m_size == 0 )
		return;
	if ( start >= m_size )
		start = m_size-1;
	if ( ((start + count) > m_size) || (count == 0) )
		count = m_size - start; 
	
	std::memset(USER_DATA+start, byte, count);
}

std::string ByteArray::toHex( uint8_t rowSize ) const
{
	std::stringstream ss;
	
	if (m_size > 0)
		for(size_t i=0; i<m_size; ++i) {
			if (rowSize>0)
				if ((i)%rowSize == 0) {
					if (i>0) ss << '\n';
					ss << std::hex << std::setw(8) << std::setfill('0') << i << ": ";
				}
				else
					ss << ' ';
			else if (i!=0)
				ss << ' ';
			ss << std::hex << std::setw(2) << std::setfill('0') << uint(USER_DATA[i]);
		}
	else
		ss << "(empty)";
				
	return ss.str();
}

std::string ByteArray::toBin( uint8_t rowSize ) const
{
	std::stringstream ss;
	
	if (m_size > 0) {
		for(size_t i=0; i<m_size; ++i) {
			if ( (rowSize>0) && (i%rowSize != 0) )
				ss << '\n' << std::hex << std::setw(8) << std::setfill('0') << i << ": ";
			else if ( (rowSize>0) && (i%rowSize != 0) )
				ss << ' ';
			else if ( (rowSize == 0) && (i!=0) )
				ss << ' ';
			ss << std::bitset<8>( USER_DATA[i] );
		}
	}
	else
		ss << "(empty)";
	
	return ss.str();
}

uint8_t ByteArray::at( size_t pos ) const
{
	PRECONDITION_ASSERT(m_data != NULL, "Object is null");
	
	if (pos < m_size)
		return USER_DATA[pos];
	else
		throw std::overflow_error("Index out of bounds");
}

uint8_t & ByteArray::operator[](size_t pos)
{
	PRECONDITION_ASSERT(m_data != NULL, "Object is null");
	
	return USER_DATA[pos];
}

uint8_t ByteArray::operator[](size_t pos) const
{
	return this->operator [](pos);
}

void ByteArray::wrapExistingData( uint8_t* data, size_t size )
{
	PRECONDITION_ASSERT(data && size, "Data is null or size is 0");
	
	clear();
	
	m_data = data;
	m_size = size;
	m_capacity = size;
	m_ownsData = false;
}
